/*
  Ejemplo de programa servidor. Atiende peticiones aritmeticas, el protocolo de transporte
  sera el comm::Transport y el contenido del mensaje sera el resutaldo de un comm::Codec con los
  (x, y, op) -> El resultado sera estos tres componente mas result.

  La conexion entre el cliente y el servidor se hace usando OpenSSL.

  Para poder probar el sistema de congestion se puede indicar un numero de milisegundos de 
  retardo aplicados a cada contestacion.

  Los clientes pueden ser: ssl_kclient.p

  Para crear los archivos de clave privada y certificado:
     http://www.openssl.org/docs/apps/CA.pl.html

   Comandos:     
      openssl req -new -x509 -out cacert.pem
   
   Para quitar la clave y que no pida por teclado:
      openssl rsa -in privkey.pem -out privkey_sin.pem
*/
#include <iostream>

#include <nemesis.h>
#include <nemesis.comm.h>

#include <nemesis.commsec.ServerSocket.h>
#include <nemesis.commsec.functions.h>

#include <test.Request.h>
#include <test.Response.h>

using namespace std;
using namespace test;

class MyCommunicator : public comm::Communicator {
public:
   MyCommunicator () {;}
   
   void setDelay (const Milisecond delay) throw () { a_delay = delay; }
   
private:
   Request a_request;
   Response a_response;
   Milisecond a_delay;
   
   void eventReceiveMessage (comm::ClientSocket &, const Message& message)
      throw (RuntimeException);
};

class ArithmeticSecureServer : public comm::Application {
public:
   ArithmeticSecureServer ();
      
private:
   MyCommunicator a_communicator;
   commsec::ServerSocket* a_serverSocket;

   void initialize () throw (RuntimeException);
   void run () throw (RuntimeException);
};

using namespace std;
using namespace nemesis::comm;

int main (int argc, const char** argv)
{
   CommandLine& commandLine (CommandLine::instanciate ());
   ArithmeticSecureServer app;
   TraceWriter* tw;

   srand (time (NULL));

   try {
      commandLine.initialize (argv, argc);
      commandLine.verify ();

      Logger::setLevel (Logger::Debug); 
      Logger::initialize ("ssl_server", tw = new nemesis::TraceWriter ("file.trace", 4048000));
      
      commsec::functions::initialize (tw);
 
      app.start ();
   }
   catch (Exception& ex) {
      cout << ex.asString () << endl;
   }
   
   return 0;
}

ArithmeticSecureServer::ArithmeticSecureServer () : 
   Application ("ssl_server", "Servidor seguro de operaciones aritmeticas", "1.0") 
{
   CommandLine& commandLine (CommandLine::instanciate ());

   commandLine.add ("cert", CommandLine::Argument::Mandatory, "Archivo que contiene el certificado");
   commandLine.add ("key", CommandLine::Argument::Mandatory, "Archivo que contiene la clave privada.");
   commandLine.add ("p", CommandLine::Argument::Mandatory, "Puerto en el que atender peticiones");
   commandLine.add ("a", CommandLine::Argument::Mandatory, "Direccion IP en la que atender");
   commandLine.add ("d", CommandLine::Argument::Mandatory, "Retardo aplicado a la contestacion");
   commandLine.add ("r", CommandLine::Argument::Optional, "Indicador de reuso de direccin", false);
   commandLine.add ("limit", CommandLine::Argument::Mandatory, "% de ocupacion que permitimos");
   commandLine.add ("verify", CommandLine::Argument::Optional, "Indicador de comprobacion del certificado cliente", false);
}

//-----------------------------------------------------------------------------------------
// Inicializa el servidor de sockets.
//-----------------------------------------------------------------------------------------
void ArithmeticSecureServer::initialize () 
   throw (RuntimeException)
{
   CommandLine& cl (CommandLine::instanciate ());

   int port = cl.getIntegerValue ("p");
   const comm::Device* device = Network::instanciate ().find (Device::asAddress (cl.getValue ("a")));

   a_serverSocket = new commsec::ServerSocket (
      commsec::Method::SSLv23, cl.getValue ("cert"), cl.getValue ("key"),
      INetAddress (device, port), cl.exists ("r")
   );

   if (cl.exists ("verify")) {
      commsec::Authentication& auth = a_serverSocket->getAuthentication ();
      auth.setVerifyPeer (true);
   }
}

//-----------------------------------------------------------------------------------------
// Atiende las peticiones.
// Cuando hay un nuevo mensaje invocar�a Communicator::eventReceiveMessage
//-----------------------------------------------------------------------------------------
void ArithmeticSecureServer::run ()
   throw (RuntimeException)
{
   CommandLine& cl (CommandLine::instanciate ());

   a_communicator.attach (a_serverSocket);
   a_communicator.setDelay (cl.getIntegerValue ("d"));

   CongestionController::instanciate ().setLimit (cl.getIntegerValue ("limit"));

   a_communicator.accept ();
}

//-----------------------------------------------------------------------------------------
// Manejador de peticiones.
// Calculara la operacin solicitada y devolvera el resultado.
//
// clientSocket: Socket cliente por el que podemos responder a la peticin.
// transport: Instancia del transporto que ha interpretado el mensaje (getMessage).
//-----------------------------------------------------------------------------------------
void MyCommunicator::eventReceiveMessage (ClientSocket& clientSocket, const Message& message)
   throw (RuntimeException)
{
   LOGMETHOD (TraceMethod tm ("MyCommunicator", "eventReceiveMessage", FILE_LOCATION));

   static int messageCounter = 0;
   static int successCounter = 0;

   int value;

   CongestionController& congestionController = CongestionController::instanciate ();

   messageCounter ++;

   if (congestionController.getAdvice (clientSocket) == CongestionController::Advice::Discard)
      return;

   successCounter ++;

   int random = rand () % (a_delay / 10);
   int sign = rand () % 2;

   if (sign == 0)
      random *= -1;

   nemesis::functions::sleep (a_delay + random);

   a_request.decode (message.getBody ());

   a_response.x = a_request.x;
   a_response.y = a_request.y;

   switch (a_response.op = a_request.op) {
      case '+':
         a_response.result = a_request.x + a_request.y;
         break;
      case '-':
         a_response.result = a_request.x - a_request.y;
         break;
      case '*':
         a_response.result = a_request.x * a_request.y;
         break;
      case '/':
         a_response.result = (a_request.y != 0) ? (a_request.x / a_request.y): 0;
         break;
   }

   string msg = nemesis::functions::asString ("%d %c %d = %d", a_request.x, a_request.op, a_request.y, a_response.result);
   Logger::debug (msg, FILE_LOCATION);

   try {
      clientSocket.send (a_response);
   }
   catch (Exception& ex) {
      ex.trace ();
   }
}

